//==============================================================================
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-08-21 17:10:20 $ $Revision: 1.13 $
//
//==============================================================================

#include <stdlib.h>
#include <unistd.h>

#include "ofc/config.h"

#include "ofc/DSocket.h"
#include "ofc/DTimer.h"

#include "DTest.h"

#define WITH_THREADS 

//-Wrapper---------------------------------------------------------------------

@interface SocketTester : Object
{
}

- init;
- TCPServer;
- TCPClient;
#ifndef WIN32
- UDPServer;
- UDPClient;
#endif
@end


@implementation SocketTester

//----------------------------------------------------------------------------

- init
{
  [super init];
  
  return self;
}

//----------------------------------------------------------------------------

- TCPServer
{
  DSocket            *local     = [DSocket new];
  DSocket            *peer      = nil;
  DInetSocketAddress *address   = [DInetSocketAddress new];

  [address any :7000];
  
  TEST([local open :[address family] :DSK_STREAM :[DSocket protocol :"tcp"]]);

  // printf("Server.Open: %d\n", [local error]);

  TEST([local reuseAddr :YES]);

  TEST([local bind :address]);

  // printf("Server.Bind: %d\n", [local error]);
  
  TEST([local listen :5]);
  
  // printf("Server.Listen: %d\n", [local error]);
  
  peer = [local accept];
  
  // printf("Server.Accept: %d\n", [local error]);
  
  TEST(peer != nil);
  
  if (peer != nil)
  {
    DData *data = [peer recv :50 :0];
    
    TEST(data != nil);

    if (data != nil)
    {
      // printf("Server.Receive: %s\n", [data data]);
    
      TEST([data bcompare :"Hello there.." :14] == 0);
      
      [data free];
    }
    
    // printf("Server.Send: %d - %d\n", [peer send :"Going to exit.." :16 :0], [peer error]);
    
    TEST([peer send :"Going to exit.." :16 :0] == 16);
    
    TEST([peer close]);
    
    [peer free];
  }

  TEST([local close]);
  
  [local free];
  [address free];

#ifdef WITH_THREADS
  objc_thread_exit();
#endif
  
  return self;
}

//----------------------------------------------------------------------------

- TCPClient
{
  DSocket            *local     = [DSocket new];
  DInetSocketAddress *address   = [DInetSocketAddress new];
  BOOL                connected = NO;

  [DTimer delay :50]; // small delay for the server to start up

  [address loopback :7000];
  
  TEST([local open :[address family] :DSK_STREAM :[DSocket protocol :"tcp"]]);
  
  // printf("Client.Open: %d\n", [local error]);
  
  TEST([local reuseAddr :YES]);
  
  connected = [local connect :address];
  
  // printf("Client.Connected: %d\n", [local error]);
  TEST(connected);
  
  if (connected)
  {
    DData *data = nil;
    
    TEST([local send :"Hello there.." :14 :0] == 14);

    // printf("Client.Send: %d - %d\n", [local send :"Hello there.." :14 :0], [local error]);
    
    data = [local recv :50 :0];
    
    TEST(data != nil);
    
    if (data != nil)
    {
      // printf("Client.Received: %s - %ld\n", [data data], [data length]);
      
      TEST([data bcompare :"Going to exit.." :16] == 0);

      [data free];
    }
    
    TEST([local shutdown :DSK_SHUT_RDWR]);
    
    TEST([local close]);
  }

  [local free];
  [address free];

#ifdef WITH_THREADS
  objc_thread_exit();
#endif

  return self;
}

//----------------------------------------------------------------------------

#ifndef WIN32
- UDPServer
{
  DSocket            *local     = [DSocket new];
  DUnixSocketAddress *address   = [DUnixSocketAddress new];
  DUnixSocketAddress *peer      = [DUnixSocketAddress new];
  DData              *data      = nil;
  
  [address filename :"udp1"];

  //printf("Open family: %d\n", [address family]);
  
  TEST([local open :[address family] :DSK_DGRAM :0]);

  //printf("Server.Open: %d\n", [local error]);

  TEST([local bind :address]);

  //printf("Server.Bind: %d\n", [local error]);
  
  data = [local recvfrom :peer :50 :0];
  
  TEST([[peer host] ccompare :"udp2"] == 0);
  
  //printf("Peername: %s\n", [[peer host] cstring]);
  
  TEST(data != nil);

  if (data != nil)
  {
    //printf("Server.Receive: %s\n", [data data]);
    
    TEST([data bcompare :"Hello there.." :14] == 0);
      
    [data free];
  }
    
  // printf("Server.Send: %d - %d\n", [local sendto :peer :"Going to exit.." :16 :0], [local error]);
    
  TEST([local sendto :peer :"Going to exit.." :16 :0] == 16);
    
  TEST([local close]);

  [address close];
  
  [local free];
  [address free];
  [peer free];
  
#ifdef WITH_THREADS
  objc_thread_exit();
#endif
  
  return self;
}

//----------------------------------------------------------------------------

- UDPClient
{
  DSocket            *local     = [DSocket new];
  DUnixSocketAddress *address   = [DUnixSocketAddress new];
  DUnixSocketAddress *peer      = [DUnixSocketAddress new];
  DData              *data      = nil;
  
  [DTimer delay :50]; // small delay for the server to start up

  [address filename :"udp2"];
  [peer    filename :"udp1"];
  
  TEST([local open :[address family] :DSK_DGRAM :0]);
  
  //printf("Client.Open: %d\n", [local error]);

  // AF_UNIX: also client bind!!!
  TEST([local bind :address]);

  //printf("Client.Bind: %d\n", [local error]);
  
  TEST([local sendto :peer :"Hello there.." :14 :0] == 14);

  //printf("Client.Send: %d - %d\n", [local sendto :peer :"Hello there.." :14 :0], [local error]);
    
  data = [local recvfrom :peer :50 :0];
    
  TEST(data != nil);
    
  if (data != nil)
  {
    //printf("Client.Received: %s - %ld\n", [data data], [data length]);
      
    TEST([data bcompare :"Going to exit.." :16] == 0);

    [data free];
  }
    
  TEST([local shutdown :DSK_SHUT_RDWR]);
    
  TEST([local close]);

  [address close];
  
  [local free];
  [address free];
  [peer free];

#ifdef WITH_THREADS
  objc_thread_exit();
#endif

  return self;
}
#endif
@end

//----------------------------------------------------------------------------

static void TCPTester()
{
#ifdef WITH_THREADS

  SocketTester *tester = [SocketTester new];

  if ((objc_thread_detach(@selector(TCPServer), tester, nil) == NULL) ||
      (objc_thread_detach(@selector(TCPClient), tester, nil) == NULL))
  {
    printf("Unable to detach threads..\n");
  }
  
#else
  
  int child = fork();
  if (child < 0)
  {
    printf("Unable to fork...\n");
  }
  else if (child == 0) // child
  {
    SocketTester *tester = [SocketTester new];
    
    [tester TCPServer];
    
    exit(0);
  }
  else // father
  {
    SocketTester *tester = [SocketTester new];
    
    [tester TCPClient];
  }
#endif
}

//----------------------------------------------------------------------------

#ifndef WIN32
static void UDPTester()
{
#ifdef WITH_THREADS

  SocketTester *tester = [SocketTester new];

  if ((objc_thread_detach(@selector(UDPServer), tester, nil) == NULL) ||
      (objc_thread_detach(@selector(UDPClient), tester, nil) == NULL))
  {
    printf("Unable to detach threads..\n");
  }
  
#else
  
  int child = fork();
  if (child < 0)
  {
    printf("Unable to fork...\n");
  }
  else if (child == 0) // child
  {
    SocketTester *tester = [SocketTester new];
    
    [tester UDPServer];
    
    exit(0);
  }
  else // father
  {
    SocketTester *tester = [SocketTester new];
    
    [tester UDPClient];
  }
#endif
}
#endif

//----------------------------------------------------------------------------

void DSocket_test()
{
  DSocket             *socket    = [DSocket new];
  DInetSocketAddress  *address   = [DInetSocketAddress new];

  STARTTEST();

#ifdef WIN32
  TEST([DSocket protocol :"pup"] == 12);
#else
  TEST([DSocket protocol :"ddp"] == 37);
#endif
  
  TEST([socket open :[address family] :DSK_STREAM :[DSocket protocol :"tcp"]]);

  TEST([socket reuseAddr         :YES ]);
  TEST([socket sendBufferSize    :4096]);
  TEST([socket receiveBufferSize :8192]);
  TEST([socket keepAlive         :YES ]);
  TEST([socket blocking          :NO  ]);

  TEST([socket reuseAddr]         == YES); 

  TEST(([socket sendBufferSize   ] == 4096) || ([socket sendBufferSize]    == 8192));
  TEST(([socket receiveBufferSize] == 8192) || ([socket receiveBufferSize] == 16384));

  TEST([socket keepAlive]         == YES);
  TEST([socket blocking]          == NO);
  
  TEST([socket close]);

  TCPTester();
  
  [DTimer delay :250];
#ifndef WIN32
  UDPTester();
  
  [DTimer delay :250];
#endif

#ifdef DSA_AF_INET6
  {
    DInet6SocketAddress *address6   = [DInet6SocketAddress new];
    BOOL                 isOpen     = NO;
    
    [address6 any :3210 :0 :0];
    
    isOpen = [socket open :[address6 family] :DSK_STREAM :[DSocket protocol :"tcp"]];
    
    TEST(isOpen); // Error indicates no IPv6 support
    
    if (isOpen)
    {
      TEST([socket reuseAddr :YES]);

      TEST([socket bind :address6]);

      TEST([socket close]);
    }
    [address6 close];
    
    [address6 free];
  }
  
  {
    DInet6SocketAddress *address6 = [DInet6SocketAddress new];
    
    int port;
    unsigned long flowInfo;
    unsigned long scopeId;
    unsigned long u32[4];
    unsigned      u16[8];
    unsigned char u8[16];
    
    [address6 any :4008 :2 :1];
    
    [address6 get :u32 :u32+1 :u32+2 :u32+3 :&port :&flowInfo :&scopeId];
    
    TEST(u32[0]   == 0);
    TEST(u32[1]   == 0);
    TEST(u32[2]   == 0);
    TEST(u32[3]   == 0);
    TEST(port     == 4008);
    TEST(flowInfo == 2);
    TEST(scopeId  == 1);

    [address6 loopback: 6430 : 1 :2];

    [address6 get16 :u16 :&port :&flowInfo :&scopeId];
    
    TEST(u16[0]   == 0);
    TEST(u16[1]   == 0);
    TEST(u16[2]   == 0);
    TEST(u16[3]   == 0);
    TEST(u16[4]   == 0);
    TEST(u16[5]   == 0);
    TEST(u16[6]   == 0);
    TEST(u16[7]   == 1);
    TEST(port     == 6430);
    TEST(flowInfo == 1);
    TEST(scopeId  == 2);
    
    [address6 get :u8 :&port :&flowInfo :&scopeId];
    
    TEST(u8[0]    == 0);
    TEST(u8[1]    == 0);
    TEST(u8[2]    == 0);
    TEST(u8[3]    == 0);
    TEST(u8[4]    == 0);
    TEST(u8[5]    == 0);
    TEST(u8[6]    == 0);
    TEST(u8[7]    == 0);
    TEST(u8[8]    == 0);
    TEST(u8[9]    == 0);
    TEST(u8[10]   == 0);
    TEST(u8[11]   == 0);
    TEST(u8[12]   == 0);
    TEST(u8[13]   == 0);
    TEST(u8[14]   == 0);
    TEST(u8[15]   == 1);
    TEST(port     == 6430);
    TEST(flowInfo == 1);
    TEST(scopeId  == 2);
    
    [address6 free];
  }
#endif
    
  [socket  free];
  [address free];
  
  STOPTEST();
}

